package elephant;

import java.io.File;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.parser.ParserConfig;

import elephant.application.Application;
import elephant.application.ApplicationImage;
import elephant.config.ConfigContext;
import elephant.config.ConfigContextImpl;
import elephant.config.ConfigManager;
import elephant.core.ThreadPool;
import elephant.job.BaseJob;
import elephant.job.JobManager;
import elephant.service.Service;
import elephant.service.rpc.RPCClientService;
import elephant.task.BaseTask;
import elephant.task.TaskManager;
import elephant.util.FileUtil;
import elephant.util.IOUtil;
import elephant.util.ObjectPrinter;

/**
 * 
 * @author icecooly
 *
 */
public class Container {
	//
	private static final String LOGO=
			"      _____ _            _                 _   	"+"\r\n"+
			"     | ____| | ___ _ __ | |__   __ _ _ __ | |_ "	+"\r\n"+
			"     |  _| | |/ _ \\ '_ \\| '_ \\ / _` | '_ \\| __|"+"\r\n"+
			"     | |___| |  __/ |_) | | | | (_| | | | | |_ "	+"\r\n"+
			"     |_____|_|\\___| .__/|_| |_|\\__,_|_| |_|\\__|"	+"\r\n"+
			"                  |_|   "							+"\r\n"+                       
			"";
	//
	private static Logger logger=LoggerFactory.getLogger(Container.class);
	//
	public static String VERSION;
	
	private static final String WORK_DIR="work";
	
	private static Container container;

	private List<Lifecycle> lifecycles;
	
	private Map<Class<? extends Server>,Server> serverMap;
	
	private Map<String,Service> serviceMap;
	
	private ThreadPool threadPool;

	private Application application;
	
	private ClassLoader applicationClassLoader;
	
	private String serverName;
	
	private String serverPath;
	
	private String configFile;
	
	private JobManager jobManager;
	
	private TaskManager taskManager;
	
	private Date startTime;
	//
	static{
		VERSION=Container.class.getPackage().getImplementationVersion();
	}
	//
	private Container(){
		lifecycles=new ArrayList<>();
		serverMap=new HashMap<>();
		threadPool=new ThreadPool();
		//
		serviceMap=new HashMap<>();
		applicationClassLoader=Thread.currentThread().getContextClassLoader();
		jobManager=new JobManager();
		taskManager=new TaskManager();
		startTime=new Date();
	}
	//
	public static Container get(){
		if(container==null){
			container=new Container();
		}
		return container;
	}
	//
	public void loadConfig() throws Exception{
		ConfigManager cm=new ConfigManager();
		ConfigContext ctx=new ConfigContextImpl();
		configFile=System.getProperty("configFile");
		if(configFile!=null){
			File file=new File(configFile);
			if(!file.exists()||!file.isFile()){
				logger.warn("can't found config.js");
				return;
			}
			logger.debug("using config "+file.getAbsolutePath());
			cm.excute(new FileReader(file),ctx);
		}
	}
	//
	public void init() throws Exception{
		logger.info("\n"+LOGO);	
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" init");
		}
		ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
		serverName=System.getProperty("serverName");
		serverPath=new File(".").getAbsolutePath();
		loadConfig();
		//
		lifecycles.add(threadPool);
		lifecycles.addAll(serverMap.values());
		lifecycles.addAll(serviceMap.values());
		if(application!=null){
			lifecycles.add(application);
		}
		lifecycles.add(jobManager);
		lifecycles.add(taskManager);
		//
		for(Lifecycle lc:lifecycles){
			lc.init();
		}
	}
	//
	public void start() throws Exception{
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" start");
		}
		//
		for(Lifecycle lc:lifecycles){
			lc.start();
		}
		//
		logger.info(this.getClass().getSimpleName()+" start with PID:{} finish {}",
				getPid(),
				dump());
	}
	//
	public void stop(){
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" stop");
		}
		try {
			Collections.reverse(lifecycles);
			for(Lifecycle lc:lifecycles){
				lc.stop();
			};
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
		IOUtil.delDirectory(WORK_DIR);
		//
		Date stopTime=new Date();
		Duration d=Duration.between(stopTime.toInstant(), startTime.toInstant());  
		logger.info(this.getClass().getSimpleName()+" stop.{} running {}",VERSION,d);
	}
	
	private static String getPid() {
	  try {
		  String jvmName = ManagementFactory.getRuntimeMXBean().getName();
		  return jvmName.split("@")[0];
	  }catch (Throwable ex) {
	   return null;
	  }
	}
	//-------------------------------------------------------------------------
	public void registerServer(Server server){
		if(serverMap.containsKey(server.getClass())){
			logger.warn("registerServer failed.already register server{}",
					server.getClass().getName());
			return;
		}
		serverMap.put(server.getClass(), server);
	}
	@SuppressWarnings("unchecked")
	public <T> T getServer(Class<?> serverClass){
		if(!serverMap.containsKey(serverClass)){
			return null;
		}
		return (T) serverMap.get(serverClass);
	}
	//
	public List<Server>getServers(){
		return new ArrayList<>(serverMap.values());
	}
	//
	//------------------------------Application--------------------------------------------
	public void loadApplication(Application application) throws Exception{	
		setApplication(application);
	}
	//
	public void loadApplication(String appArchiveFile) throws Exception{
		File archiveFile=new File(appArchiveFile);
		if(!archiveFile.exists()){
			throw new IllegalArgumentException("can not find app archive file:"+appArchiveFile); 
		}
		File workDir=new File(serverPath,WORK_DIR);
		workDir=FileUtil.createEmptyDir(workDir.getAbsolutePath());
		logger.debug("using package file:"+archiveFile.getAbsolutePath());
		logger.debug("using temp app dir:"+workDir.getAbsolutePath());
		FileUtil.unzip(appArchiveFile,workDir.getAbsolutePath());
		File appDirs[]=workDir.listFiles();
		if(appDirs==null||appDirs.length!=1){
			throw new IllegalArgumentException("can not load application from:"+workDir); 
		}
		//
		ApplicationImage image=ApplicationImage.load(appDirs[0].getAbsolutePath());
		if(image!=null){
			application=image.loadApplication();
			if(application!=null){
				setApplication(application);
			}else{
				throw new IllegalArgumentException("application class not found"); 
			}
		}
	}
	//
	public void setApplication(Application application) {
		this.application = application;
		applicationClassLoader=this.application.getClass().getClassLoader();
		Thread.currentThread().setContextClassLoader(applicationClassLoader);					
	}
	//
	public Application getApplication(){
		return application;
	}
	//
	public ClassLoader getApplicationClassLoader() {
		return applicationClassLoader;
	}
	//
	public void setAppClassLoader(ClassLoader appClassLoader) {
		this.applicationClassLoader = appClassLoader;
	}
	//
	//---------------------------Resource-----------------------------------------------
	//
	public void registerService(Service srv) {
		serviceMap.put(srv.getClass().getSimpleName(), srv);
		if(logger.isDebugEnabled()){
			logger.debug("registerService "+srv.getClass().getSimpleName());
		}
	}
	//
	@SuppressWarnings("unchecked")
	public <T> T  getService(Class<? extends Service> clazz){
		for(Service e:serviceMap.values()){
			if(clazz.equals(e.getClass())){
				return (T) e;
			}
			if(clazz.isAssignableFrom(e.getClass())){
				return (T) e;
			}
		}
		return null;
	}
	//
	@SuppressWarnings("unchecked")
	public <T> List<T>  getServices(Class<? extends Service> clazz){
		List<T> list=new ArrayList<>();
		for(Service e:serviceMap.values()){
			if(clazz.equals(e.getClass())){
				list.add((T) e);
			}
			if(clazz.isAssignableFrom(e.getClass())){
				list.add((T) e);
			}
		}
		return list;
	}
	//
	//--------------------------ThreadPool-------------------------------------------
	//
	public void executeThreadWorker(Runnable worker){
		threadPool.execute(worker);
	}
	//
	public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
			long initialDelay, long period, TimeUnit unit) {
		return threadPool.scheduleAtFixedRate(command,
				initialDelay, period, unit);
	}
	//
	public void setWorkerPoolSize(int size){
		threadPool.setWorkerPoolSize(size);
	}
	//
	public void setMaxWorkerPoolSize(int size){
		threadPool.setMaxWorkerPoolSize(size);
	}
	//
	public void setScheduledPoolSize(int size){
		threadPool.setScheduledPoolSize(size);
	}
	//---------------------------Action----------------------------------------------
	public <T> T createSyncRemote(Class<T>clazz,String clusterName){
		logger.debug("createSyncRemote clazz:{},clusterName:{}",clazz.getSimpleName(),clusterName);
		RPCClientService rpcResource=getService(RPCClientService.class);
		return rpcResource.createSyncRemote(clazz,clusterName);
	}
	//
	public <T> T createAsyncRemote(Class<T>clazz,String clusterName){
		logger.debug("createAsyncRemote clazz:{},clusterName:{}",clazz.getSimpleName(),clusterName);
		RPCClientService rpcResource=getService(RPCClientService.class);
		return rpcResource.createAsyncRemote(clazz,clusterName);
	}
	//----------------------------Job----------------------------------------------
	public void registerJob(BaseJob job){
		jobManager.registerJob(job);
	}
	//
	public boolean runJob(String id){
		return jobManager.runJob(id);
	}
	//
	public JobManager getJobManager() {
		return jobManager;
	}
	//----------------------------Task----------------------------------------------
	public void registerTask(BaseTask task){
		taskManager.registerTask(task);
	}
	//
	public boolean runTask(String id){
		return taskManager.runTask(id);
	}
	//
	public TaskManager getTaskManager() {
		return taskManager;
	}
	//
	//----------------------------Others----------------------------------------------	
	//
	public String getServerName() {
		return serverName;
	}
	//
	public String getServerPath() {
		return serverPath;
	}
	//
	public Date getStartTime() {
		return startTime;
	}
	//
	public static String getStackTrace(Throwable t){
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        t.printStackTrace(pw);
        pw.flush();
        sw.flush();
        return sw.toString();
    }
	//
	public String dump(){
		ObjectPrinter op=new ObjectPrinter();
		op.println("");
		op.println(dumpSystemProperty());
		op.section(this.getClass().getSimpleName());
		op.print("version",VERSION);
		op.print("serverName:",serverName);
		op.print("serverPath:",serverPath);
		op.print("configFile",configFile);
		op.print("appClassloader:",applicationClassLoader);
		op.print("startTime",startTime);
		op.println("");
		//
		lifecycles.forEach(lc->{
			op.println(lc.dump());
		});
		//
		Date endTime=new Date();
		Duration d=Duration.between(startTime.toInstant(),endTime.toInstant());
		op.section("");
		op.println("container start using "+d.toMillis()+"ms");
		return op.toString();
	}
	//
	private String dumpSystemProperty(){
		ObjectPrinter op=new ObjectPrinter();
		op.section("System Property");
		op.print("java_vendor",System.getProperty("java.vendor"));     
		op.print("java_home",System.getProperty("java.home"));     
	    op.print("java_class_version",System.getProperty("java.class.version"));     
	    op.print("java_class_path",System.getProperty("java.class.path"));     
	    op.print("os_name",System.getProperty("os.name"));     
	    op.print("os_arch",System.getProperty("os.arch"));     
	    op.print("os_version",System.getProperty("os.version"));     
	    op.print("user_name",System.getProperty("user.name"));     
	    op.print("user_home",System.getProperty("user.home"));     
	    op.print("user_dir",System.getProperty("user.dir"));     
	    op.print("java_vm_specification_version",System.getProperty("java.vm.specification.version"));     
	    op.print("java_vm_specification_vendor",System.getProperty("java.vm.specification.vendor"));     
	    op.print("java_vm_specification_name",System.getProperty("java.vm.specification.name"));     
	    op.print("java_vm_version",System.getProperty("java.vm.version"));     
	    op.print("java_vm_vendor",System.getProperty("java.vm.vendor"));     
	    op.print("java_vm_name",System.getProperty("java.vm.name"));     
	    op.print("java_ext_dirs", System.getProperty("java.ext.dirs"));     
	    op.print("file_separator",System.getProperty("file.separator"));     
	    op.print("path_separator",System.getProperty("path.separator"));     
	   return op.toString();
	}
	//
	public static void main(String[] args) {
		Runtime.getRuntime().addShutdownHook(new Thread("ShutDownHook"){
			@Override
			public void run() {
				try {
					Container.get().stop();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		//
		Container elephant=Container.get();
		try{
			elephant.init();
			elephant.start();
		}catch (Exception e) {
			if(logger.isErrorEnabled()){
				logger.error(getStackTrace(e),e);
			}
			System.exit(1);
		}
	}
}
